import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;
import java.io.DataInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.JFrame;

public class GameClient implements Runnable, Observer {

	public GameFrame game_frame;
	public GameMenu game_menu;
	private Socket clientSocket = null;
	private LinkedBlockingDeque<int[][]> messages;
	public volatile ObjectInputStream object_input_stream = null;
	public volatile ObjectOutputStream object_output_stream = null;

	private String game_username;
	private String opponent_name;
	private String host = "192.168.0.108"; // IP of Server Machine

	private int portNumber = 2222;
	private int state_counter = -1;
	private int client_number = 0;
	private int number_of_total_hits = 0;
	private int number_of_successful_hits = 0;
	private final int max_number_of_hits_needed = 39;

	private int[][] game_matrix_table;
	public int[][] game_matrix_response_table;

	private boolean game_ended = false;
	private boolean game_client_who_starts = false;
	private volatile boolean matrix_received = false;
	private volatile boolean can_update_game_table = false;

	public GameClient(GameFrame game_frame, GameMenu game_menu) {
		this.game_menu = game_menu;
		this.game_frame = game_frame;

		this.messages = new LinkedBlockingDeque<int[][]>();
		this.game_matrix_response_table = new int[20][20];

		this.game_frame.game_table.game_player_ready_button_observable
				.addObserver(this);
		this.game_frame.game_table.game_turn_ready_button_observable
				.addObserver(this);
		this.game_menu.start_button_observable.addObserver(this);
		this.game_menu.set_username_frame.game_menu_set_username_observable
				.addObserver(this);

		this.game_username = new String("default_player_name");

		try {
			this.clientSocket = new Socket(this.host, this.portNumber);
		} catch (UnknownHostException e) {
			System.err.println("Don't know about host " + host);
		} catch (IOException e) {
			System.err
					.println("Couldn't get I/O for the connection to the host "
							+ host);
		}

		try {
			this.object_output_stream = new ObjectOutputStream(
					this.clientSocket.getOutputStream());
			this.object_input_stream = new ObjectInputStream(
					this.clientSocket.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}

		if (this.object_input_stream != null && this.clientSocket != null) {
			try {
				this.game_client_who_starts = (boolean) this.object_input_stream
						.readObject();
				this.game_frame.game_table.game_flow_control_panel.game_flow_control_who_starts = this.game_client_who_starts;
				if (this.game_client_who_starts == false) {
					this.client_number = 2;
				} else {
					this.client_number = 1;
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			new Thread(this).start();
		}
		this.create_game_menu();
	}

	public void run() {

		while (true) {
			if (this.client_number == 1) {
				switch (this.state_counter) {
				case -1:
					try {
						String opponent_name = (String) this.object_input_stream
								.readObject();
						this.opponent_name = opponent_name;
						this.game_frame.game_table.game_flow_control_panel.opponent_name_label
								.setText("Your opponent is: "
										+ this.opponent_name);
					} catch (IOException e) {
						System.err.println("IOException:  " + e);
					} catch (ClassNotFoundException e) {
						System.err.println("ClassNotFoundException: " + e);
						e.printStackTrace();
					}
					this.state_counter++;
					break;
				case 0:
					// receive feedback
					try {
						this.messages
								.addFirst((int[][]) this.object_input_stream
										.readObject());
					} catch (IOException e) {
						System.err.println("IOException:  " + e);
					} catch (ClassNotFoundException e) {
						System.err.println("ClassNotFoundException: " + e);
						e.printStackTrace();
					}
					this.game_matrix_table = this.messages.getFirst();
					for (int i = 0; i < 20; i++)
						for (int j = 0; j < 20; j++) {
							if (this.game_matrix_table[i][j] == 2)
								this.game_frame.game_table.game_flow_display.MatrixTable2[i][j] = this.game_matrix_table[i][j];
						}
					this.game_frame.game_table.game_flow_display.to_repaint = true;
					this.state_counter++;
					break;
				case 1:
					// receive hit
					try {
						this.messages
								.addFirst((int[][]) this.object_input_stream
										.readObject());
					} catch (IOException e) {
						System.err.println("IOException:  " + e);
					} catch (ClassNotFoundException e) {
						System.err.println("ClassNotFoundException: " + e);
						e.printStackTrace();
					}
					this.state_counter++;
					break;
				case 2:
					// process hit && send feedback
					int[][] response_table = new int[20][20];
					this.game_matrix_table = this.messages.getFirst();
					for (int i = 0; i < 20; i++)
						for (int j = 0; j < 20; j++) {
							if (this.game_matrix_table[i][j] == 1) {
								if (this.game_frame.game_table.game_flow_display.MatrixTable[i][j] == 3) {
									this.number_of_successful_hits++;
									this.game_frame.game_table.game_flow_display.MatrixTable[i][j] = 2;
									if (this.number_of_successful_hits == this.max_number_of_hits_needed) {
										System.out.println("Disposed");
										this.game_frame.setVisible(false);
										this.game_frame.game_table
												.setVisible(false);
										this.game_frame.dispose();
										this.game_ended = true;
									}
									if (this.game_ended == true) {
										response_table[i][j] = 0;
									} else {
										response_table[i][j] = 2;
									}
								}
								if (this.game_frame.game_table.game_flow_display.MatrixTable[i][j] == 0) {
									this.game_frame.game_table.game_flow_display.MatrixTable[i][j] = 1;
									if (this.game_ended == true) {
										response_table[i][j] = 0;
									} else {
										response_table[i][j] = 1;
									}
								}
							}
						}
					this.game_frame.game_table.game_flow_display.to_repaint = true;
					try {
						this.object_output_stream.writeObject(response_table);
						this.object_output_stream.flush();
					} catch (IOException e) {
						System.out.println(e);
					}
					this.game_frame.game_table.game_flow_control_panel.is_it_your_turn = true;
					this.state_counter = 0;
					break;
				}
			}
			if (this.client_number == 2) {
				switch (this.state_counter) {
				case -1:
					// receive opponent name
					try {
						String opponent_name = (String) this.object_input_stream
								.readObject();
						this.opponent_name = opponent_name;
						this.game_frame.game_table.game_flow_control_panel.opponent_name_label
								.setText("Your opponent is: "
										+ this.opponent_name);
					} catch (IOException e) {
						System.err.println("IOException:  " + e);
					} catch (ClassNotFoundException e) {
						System.err.println("ClassNotFoundException: " + e);
						e.printStackTrace();
					}
					this.state_counter++;
					break;
				case 0:
					// receive hit
					try {
						this.messages
								.addFirst((int[][]) this.object_input_stream
										.readObject());
					} catch (IOException e) {
						System.err.println("IOException:  " + e);
					} catch (ClassNotFoundException e) {
						System.err.println("ClassNotFoundException: " + e);
						e.printStackTrace();
					}
					this.state_counter++;
					break;
				case 1:
					// process hit && send feedback
					int[][] response_table = new int[20][20];
					this.game_matrix_table = this.messages.getFirst();
					for (int i = 0; i < 20; i++)
						for (int j = 0; j < 20; j++) {
							if (this.game_matrix_table[i][j] == 1) {
								if (this.game_frame.game_table.game_flow_display.MatrixTable[i][j] == 3) {
									this.number_of_successful_hits++;
									this.game_frame.game_table.game_flow_display.MatrixTable[i][j] = 2;
									if (this.number_of_successful_hits == this.max_number_of_hits_needed) {
										System.out.println("Disposed");
										this.game_frame.setVisible(false);
										this.game_frame.game_table
												.setVisible(false);
										this.game_frame.dispose();
										this.game_ended = true;
									}
									if (this.game_ended == true) {
										response_table[i][j] = 0;
									} else {
										response_table[i][j] = 2;
									}
								}
								if (this.game_frame.game_table.game_flow_display.MatrixTable[i][j] == 0) {
									this.game_frame.game_table.game_flow_display.MatrixTable[i][j] = 1;
									if (this.game_ended == true) {
										response_table[i][j] = 0;
									} else {
										response_table[i][j] = 1;
									}
								}
							}
						}
					this.game_frame.game_table.game_flow_display.to_repaint = true;
					try {
						this.object_output_stream.writeObject(response_table);
						this.object_output_stream.flush();
					} catch (IOException e) {
						System.out.println(e);
					}
					this.game_frame.game_table.game_flow_control_panel.is_it_your_turn = true;
					this.state_counter++;
					break;
				case 2:
					// receive feedback
					try {
						this.messages
								.addFirst((int[][]) this.object_input_stream
										.readObject());
					} catch (IOException e) {
						System.err.println("IOException:  " + e);
					} catch (ClassNotFoundException e) {
						System.err.println("ClassNotFoundException: " + e);
						e.printStackTrace();
					}
					this.game_matrix_table = this.messages.getFirst();
					for (int i = 0; i < 20; i++)
						for (int j = 0; j < 20; j++) {
							if (this.game_matrix_table[i][j] == 2)
								this.game_frame.game_table.game_flow_display.MatrixTable2[i][j] = this.game_matrix_table[i][j];
						}
					this.game_frame.game_table.game_flow_display.to_repaint = true;
					this.state_counter = 0;
					break;
				}
			}
		}
	}

	public void send_game_player_ready() {
		boolean player_ready = true;
		try {
			this.object_output_stream.writeObject((boolean) player_ready);
			this.object_output_stream.flush();
			this.object_output_stream.writeObject((String) this.game_username);
			this.object_output_stream.flush();
		} catch (IOException error) {
			System.out.println(error);
		}
	}

	@Override
	public void update(Observable o, Object arg) {

		if (o.equals(this.game_frame.game_table.game_player_ready_button_observable)) {
			this.send_game_player_ready();
		}
		if (o.equals(this.game_frame.game_table.game_turn_ready_button_observable)) {
			int[][] table = new int[20][20];
			for (int i = 0; i < 20; i++) {
				for (int j = 0; j < 20; j++)
					table[i][j] = this.game_frame.game_table.game_flow_display.MatrixTable2[i][j];
			}
			try {
				this.object_output_stream.writeObject(table);
				this.object_output_stream.flush();
			} catch (IOException e) {
				System.out.println(e);
			}
		}
		if (o.equals(this.game_menu.start_button_observable)) {
			this.start_game_game();
		}
		if (o.equals(this.game_menu.set_username_frame.game_menu_set_username_observable)) {
			this.game_username = this.game_menu.set_username_frame.user_name;
		}
	}

	public void create_game_menu() {
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		int width = (int) screenSize.getWidth();
		int height = (int) screenSize.getHeight();
		game_menu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		game_menu.setSize(width, height);
		game_menu.setVisible(true);
	}

	public void start_game_game() {
		this.game_menu.setVisible(false);
		this.game_frame.game_table.setVisible(true);
	}

	public static void main(String args[]) {

		GameFlowDisplay flow_display = new GameFlowDisplay(20);
		GameFlowControl flow_control = new GameFlowControl();
		GameTable game_table = new GameTable(flow_display, flow_control);
		game_table.setVisible(false);
		new Thread(game_table).start();
		GameFrame game_frame = new GameFrame(game_table);
		GameMenuSetUserNameFrame game_menu_set_user_name_option = new GameMenuSetUserNameFrame();
		GameMenu game_menu = new GameMenu(game_menu_set_user_name_option);
		GameClient client = new GameClient(game_frame, game_menu);
		new Thread(client.game_frame.game_table).start();
	}
}
